﻿#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace AbstractClass.Collections.Generic
{
    /// <summary>
    /// Represents a collection of elements arranged into a fixed number of rows and columns.
    /// </summary>
    /// <typeparam name="T">The type of the data in the collection.</typeparam>
    [Serializable]
    public class Matrix<T> : ICloneable, IEnumerable<T>
    {
        /// <summary>
        /// The elements in the <see cref="Matrix&lt;T&gt;"/>.
        /// </summary>
        protected readonly T[] Data;

        /// <summary>
        /// Initializes a new instance of the <see cref="Matrix&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="rowCount">The number of rows.</param>
        /// <param name="columnCount">The number of columns.</param>
        /// <exception cref="ArgumentOutOfRangeException">Number of rows / columns is not a positive integer.</exception>
        public Matrix(int rowCount, int columnCount)
        {
            if (rowCount < 0 || columnCount < 0)
                throw new ArgumentOutOfRangeException(LanguageResource.MatrixPositiveColRow);

            RowCount = rowCount;
            ColumnCount = columnCount;
            Data = new T[rowCount * columnCount];
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Matrix&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="rowCount">The number of rows.</param>
        /// <param name="columnCount">The number of columns.</param>
        /// <param name="data">An array of type <typeparamref name="T"/> whose elements are copied
        /// to the new <see cref="Matrix&lt;T&gt;"/>.	</param>
        /// <exception cref="ArgumentOutOfRangeException">Number of rows / columns is not a positive integer.
        /// <para/>The length of the <paramref name="data"/> is not equal to the product of <paramref name="rowCount"/> and <paramref name="columnCount"/>.</exception>
        protected Matrix(int rowCount, int columnCount, T[] data)
        {
            if (rowCount < 0 || columnCount < 0)
                throw new ArgumentOutOfRangeException(LanguageResource.MatrixPositiveColRow);

            if (rowCount * columnCount != data.Length)
                throw new ArgumentOutOfRangeException("data");

            RowCount = rowCount;
            ColumnCount = columnCount;
            Data = data;
        }


        /// <summary>
        /// Gets or sets the number of rows in the matrix.
        /// </summary>
        /// <value>The number of row.</value>
        public int RowCount { get; protected set; }

        /// <summary>
        /// Gets or sets the number of columns in the matrix.
        /// </summary>
        /// <value>The number of columns.</value>
        public int ColumnCount { get; protected set; }

        /// <summary>
        /// Gets the total number of data in the matrix.
        /// </summary>
        /// <value>The number of data.</value>
        public int Count
        {
            get { return RowCount * ColumnCount; }
        }

        /// <summary>
        /// Gets or sets a elements at the specified position.
        /// </summary>
        /// <param name="row">The specified row to access the element.</param>
        /// <param name="column">The specified column to access the element.</param>
        public T this[int row, int column]
        {
            get { return Data[row + column * RowCount]; }
            set { Data[row + column * RowCount] = value; }
        }

        #region ICloneable Members

        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

        #region IEnumerable<T> Members

        /// <summary>
        ///   Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///   A <see cref = "IEnumerator&lt;T&gt;" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < RowCount; i++)
            {
                for (int j = 0; j < ColumnCount; j++)
                {
                    yield return this[i, j];
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Creates a new <see cref="Matrix&lt;T&gt;"/> coping the data from the specified one.
        /// </summary>
        /// <param name="matrix">The matrix whose data are copied to a new <see cref="Matrix&lt;T&gt;"/>.</param>
        /// <returns>A new <see cref="Matrix&lt;T&gt;"/> containing the data of the specified <see cref="Matrix&lt;T&gt;"/>.</returns>
        public static Matrix<T> Create(Matrix<T> matrix)
        {
            var data = new T[matrix.Data.LongLength];
            matrix.Data.CopyTo(data, 0);
            return new Matrix<T>(matrix.RowCount, matrix.ColumnCount, data);
        }

        /// <summary>
        /// Creates a new <see cref="Matrix&lt;T&gt;"/> with the specified dimensions and fills
        /// the matrix with a specified <paramref name="value"/>.
        /// </summary>
        /// <param name="rowCount">The number of rows.</param>
        /// <param name="columnCount">The number of columns.</param>
        /// <param name="value">The value to be used to fill the new <see cref="Matrix&lt;T&gt;"/>.</param>
        /// <returns>A new <see cref="Matrix&lt;T&gt;"/> with a specified dimensions filled with the specified 
        /// <paramref name="value"/>.</returns>
        /// <exception cref="ArgumentException">Number of rows / columns is not a positive integer.</exception>
        public static Matrix<T> Create(int rowCount, int columnCount, T value)
        {
            if (rowCount < 0 || columnCount < 0)
                throw new ArgumentOutOfRangeException(LanguageResource.MatrixPositiveColRow);

            var data = new T[rowCount * columnCount];
            for (int i = 0; i < data.Length; ++i)
                data[i] = value;

            return new Matrix<T>(rowCount, columnCount, data);
        }

        /// <summary>
        /// Verifies the equality of the dimensions of <paramref name="matrix"/> with that of this instance.
        /// </summary>
        /// <param name="matrix">The specified matrix whose dimensions to be verified for equality 
        /// with the same of this instance.</param>
        /// <exception cref="MatrixSizeMismatchException">Dimensions is not equal to that of this instance.</exception>
        protected void VerifySize(Matrix<T> matrix)
        {
            if (RowCount != matrix.RowCount)
                throw new MatrixSizeMismatchException();
            if (ColumnCount != matrix.ColumnCount)
                throw new MatrixSizeMismatchException();
        }

        /// <summary>
        /// Gets all the elements of this instance as an array of type <typeparamref name="T"/>.
        /// </summary>
        /// <returns>An array of type <typeparamref name="T"/> containing all elements of this instance.</returns>
        public T[] GetData()
        {
            return Data;
        }

        /// <summary>
        /// Creates a clone of this instance.
        /// </summary>
        /// <returns>A new <see cref="Matrix&lt;T&gt;"/> which is a clone of this instance.</returns>
        public Matrix<T> Clone()
        {
            return new Matrix<T>(
                RowCount,
                ColumnCount,
                (T[]) Data.Clone()
                );
        }

        /// <summary>
        /// Calculates the trace of this instance.
        /// </summary>
        /// <returns>Trace of this instance.</returns>
        /// <exception cref="InvalidOperationException">Matrix is not a square matrix.</exception>
        public T Trace()
        {
            if (RowCount != ColumnCount)
                throw new InvalidOperationException();

            var m = new T[RowCount];
            for (int i = 0; i < RowCount; ++i)
                m[i] = this[i, i];

            var calc = new GenericCalculator<T, T, T>();
            return m.Aggregate(calc.Add);
        }

        /// <summary>
        /// Calculates the transpose matrix of this instance.
        /// </summary>
        /// <returns>A new <see cref="Matrix&lt;T&gt;"/> which is equal to the transpose of this instance.</returns>
        public Matrix<T> Transpose()
        {
            var m = new Matrix<T>(ColumnCount, RowCount);
            for (int i = 0; i < RowCount; ++i)
                for (int j = 0; j < ColumnCount; ++j)
                    m[j, i] = this[i, j];
            return m;
        }

        /// <summary>
        /// Returns a string that represents the dimension of the <see cref="Matrix&lt;T&gt;"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="String"/> representing the dimension of the matrix.
        /// </returns>
        public override string ToString()
        {
            return String.Format("({0}x{1})", RowCount, ColumnCount);
        }

        /// <summary>
        /// Writes the elements of the matrix using a <see cref="TextWriter"/>.
        /// </summary>
        /// <param name="writer">The writer to write the elements of the matrix.</param>
        public void WriteMatrix(TextWriter writer)
        {
            for (int i = 0; i < RowCount; ++i)
            {
                for (int j = 0; j < ColumnCount; ++j)
                {
                    if (j != 0)
                        writer.Write(" ");
                    writer.Write(this[i, j]);
                }
                writer.WriteLine();
            }
        }

        /// <summary>
        /// Writes the elements of the matrix using a <see cref="TextWriter"/> formatted by the 
        /// <paramref name="formatString"/>.
        /// </summary>
        /// <param name="writer">The writer to write the elements of the matrix.</param>
        /// <param name="formatString">The format string.</param>
        public void WriteMatrix(TextWriter writer, string formatString)
        {
            for (int i = 0; i < RowCount; ++i)
            {
                for (int j = 0; j < ColumnCount; ++j)
                {
                    if (j != 0)
                        writer.Write(" ");
                    writer.Write(formatString, this[i, j]);
                }
                writer.WriteLine();
            }
        }

        /// <summary>
        /// Gets the diagonal elements of this instance.
        /// </summary>
        /// <returns>A collection of elements which are at the diagonals of this matrix.</returns>
        /// <exception cref="InvalidOperationException">Matrix is not a square matrix.</exception>
        public IEnumerable<T> GetDiagonal()
        {
            if (RowCount != ColumnCount)
                throw new InvalidOperationException(LanguageResource.MatrixInvalidDiagonalOperation);
            for (int i = 0; i < RowCount; ++i)
            {
                yield return this[i, i];
            }
        }
    }
}